package com.food.restaurantpro.connection;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.food.restaurantpro.managingnetwork.ManagingNetwork;
import com.food.restaurantpro.objects.Reservation;
import com.food.restaurantpro.objects.UserInfo;

import android.os.AsyncTask;
import android.util.Log;

public class DataHelper extends Observable implements IServerService {
	private static final String TAG = "DataHelper";
	private final int MAX_SIZE_POOL = 3;

	private volatile static DataHelper uniqueInstance;

	public static int TIME_OUT_CONNECTION = 20000;
	private Map<String, HttpTask> mPoolExcutingTask;
	private Queue<HttpTask> mWaitingTask;

	public static DataHelper getInstance() {
		if (uniqueInstance == null) {
			synchronized (DataHelper.class) {
				if (uniqueInstance == null) {
					uniqueInstance = new DataHelper();
				}
			}
		}
		return uniqueInstance;
	}

	@SuppressWarnings("serial")
	public DataHelper() {
		super();
		mPoolExcutingTask = new LinkedHashMap<String, DataHelper.HttpTask>(
				MAX_SIZE_POOL) {
			@Override
			protected boolean removeEldestEntry(
					java.util.Map.Entry<String, HttpTask> eldest) {
				if (size() > MAX_SIZE_POOL) {
					Log.e(TAG, "remove eldest task "
							+ eldest.getValue().getUrl()
							+ " current size is : " + size());
					eldest.getValue().disconect();
					return true;
				} else {
					return false;
				}
			}
		};
		mWaitingTask = new ConcurrentLinkedQueue<DataHelper.HttpTask>();
	}

	private void excute(String url, MethodParams methodparam) {
		//url not exist in mPoolExcutingTask
		if (mPoolExcutingTask.get(url) == null) {
			HttpTask task = new HttpTask(url, methodparam);
			mPoolExcutingTask.put(url, task);
			task.execute((Void) null);
		}
		Log.e(TAG, "PLACE TASK TO POOL : " + url + " current size : "
				+ mPoolExcutingTask.size() + ". Error when current size > " + MAX_SIZE_POOL);
	}

	private void excute(String url, Map<String, String> data,
			MethodParams methodparam) {
		if (mPoolExcutingTask.get(url) == null) {
			HttpTask task = new HttpTask(url, methodparam, data);
			mPoolExcutingTask.put(url, task);
			task.execute((Void) null);
		}
		Log.e(TAG, "PLACE TASK TO POOL : " + url + " current size : "
				+ mPoolExcutingTask.size() + ". Error when current size > " + MAX_SIZE_POOL);
	}
	
	private class HttpTask extends AsyncTask<Void, Void, DataResult> {
		MethodParams mMethodParam;
		String mHttpUrl;
		Map<String, String> mDataPost;
		HttpURLConnection mHttpUrlConnection;

		public HttpTask(String url, MethodParams params) {
			mHttpUrl = url;
			mMethodParam = params;
		}

		public HttpTask(String url, MethodParams params,
				Map<String, String> datapost) {
			mHttpUrl = url;
			mDataPost = datapost;
			mMethodParam = params;
		}

		public String getUrl() {
			return mHttpUrl;
		}

		public void disconect() {
			if (mHttpUrlConnection != null)
				mHttpUrlConnection.disconnect();
			cancel(true);
			mWaitingTask.add(new HttpTask(mHttpUrl, mMethodParam, mDataPost));
			Log.e(TAG, "waiting queue size: " + mWaitingTask.size());

		}

		@Override
		protected void onProgressUpdate(Void... values) {
			// TODO Auto-generated method stub
			super.onProgressUpdate(values);
		}

		@Override
		protected DataResult doInBackground(Void... params) {
			if (!ManagingNetwork.isNetworkAvailable()) {
				DataResult result = new DataResult();
				result.setErrorCode(ErrorCode.NETWORK_ERROR);
				result.setMethodParam(mMethodParam);
				return result;
			}
			try {
				URL u = new URL(mHttpUrl);
				mHttpUrlConnection = (HttpURLConnection) u.openConnection();
				mHttpUrlConnection.setConnectTimeout(TIME_OUT_CONNECTION);
				mHttpUrlConnection.setReadTimeout(TIME_OUT_CONNECTION);
				mHttpUrlConnection.setAllowUserInteraction(false);
				mHttpUrlConnection.setInstanceFollowRedirects(true);
				mHttpUrlConnection.setDoInput(true);
				return request(mHttpUrlConnection, getParamsString(mDataPost),
						mMethodParam);
			} catch (FileNotFoundException e) {
				Log.e(TAG, "ERROR doInBackground " + e.toString());
				DataResult result = new DataResult();
				result.setErrorCode(ErrorCode.SERVER_ERROR);
				result.setMethodParam(mMethodParam);
				return result;
			} catch (IOException e) {
				Log.e(TAG, "ERROR doInBackground " + e.toString());
				DataResult result = new DataResult();
				result.setErrorCode(ErrorCode.TIME_OUT);
				result.setMethodParam(mMethodParam);
				return result;
			} finally {
				if (mHttpUrlConnection != null) {
					mHttpUrlConnection.disconnect();
				}
			}
		}

		@Override
		protected void onPostExecute(DataResult result) {

			mPoolExcutingTask.remove(mHttpUrl);
			if (mPoolExcutingTask.size() < MAX_SIZE_POOL) {
				HttpTask task = mWaitingTask.poll();
				if (task != null) {
					mPoolExcutingTask.put(task.getUrl(), task);
					task.execute((Void) null);
				}
			}

			if (result != null) {
				// isOnlyForCache = true: when call API but not notifyObservers
				// to update
				if (mMethodParam.isOnlyForCache() == false) {
					setChanged();
					notifyObservers(result);
				}
			} else {
				if (mMethodParam.isOnlyForCache() == false) {
					setChanged();
					notifyObservers(result);
				}
				Log.e(TAG, "http task result = null");
			}
		}
	}

	private DataResult request(HttpURLConnection conn, String postData,
			MethodParams methodParams) throws IOException {

		DataResult result;
		if (postData != null) {
			conn.setRequestMethod("POST");
			conn.setDoOutput(true);
			OutputStream out = new BufferedOutputStream(conn.getOutputStream());
			out.write(postData.getBytes());
			out.flush();
			out.close();
		} else {
			conn.setRequestMethod("GET");
		}

		int httpCode = conn.getResponseCode();
		if (httpCode == HttpURLConnection.HTTP_OK
				|| httpCode == HttpURLConnection.HTTP_CREATED) {
			InputStream in = new BufferedInputStream(conn.getInputStream());
			result = ParserDataHelper.getInstance().parse(in, methodParams);
			if (result == null)
				return null;

			result.setMethodParam(methodParams);
			return result;
		} else if (httpCode == HttpURLConnection.HTTP_CLIENT_TIMEOUT
				|| httpCode == HttpURLConnection.HTTP_GATEWAY_TIMEOUT) {
			Log.e("DATAHELPER REQUEST HTTP_CLIENT_TIMEOUT or HTTP_GATEWAY_TIMEOUT",
					httpCode + "");
			result = new DataResult();
			result.setMethodParam(methodParams);
			result.setErrorCode(ErrorCode.TIME_OUT);
		} else {
			Log.e("DATAHELPER REQUEST SERVER ERROR", httpCode + "");
			result = new DataResult();
			result.setMethodParam(methodParams);
			result.setErrorCode(ErrorCode.SERVER_ERROR);
		}
		return result;
	}

	private String getParamsString(Map<String, String> params) {
		if (params == null)
			return null;
		String paramsString = "";
		for (String key : params.keySet()) {
			try {
				String value = params.get(key);
				// paramsString += key + "=" + URLEncoder.encode(value, "UTF-8") + "&";
				paramsString += key + "=" + value + "&";
			} catch (Exception e) {
				Log.e(TAG, "ERROR getParamsString()" + paramsString
						+ "_" + key);
			}
		}
		return paramsString.substring(0, paramsString.length() - 1);
	}

	@Override
	public void getCurrentTime() {
		excute(Urls.LINK_TO_GET_CURRENT_TIME, new MethodParams(Methods.getCurrentTime));
	}
	
	@Override
	public void getAllPromotions() {
		excute(Urls.LINK_TO_GET_PROMOTION, new MethodParams(Methods.getAllPromotions));
	}

	@Override
	public void getUpdatePromotion(String time, String promotionIDList) {
		String url = String.format(Urls.LINK_TO_CHECK_UPDATE_PROMOTION, time, promotionIDList);
		excute(url, new MethodParams(Methods.getUpdatePromotion));
	}

	@Override
	public void getAllDishCategory() {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_GET_ALL_DISH_CATEGORY, new MethodParams(Methods.getAllDishCategory));
	}

	@Override
	public void getAllDishs() {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_GET_ALL_DISHS, new MethodParams(Methods.getAllDishs));
	}
	
	@Override
	public void login(String userCode) {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_LOGIN + userCode, new MethodParams(Methods.login));
	}
	
	@Override
	public void updateUserInfo(UserInfo userInfo) {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_UPDATE_USER_INFO + userInfo.getUpdateInfoParams(), new MethodParams(Methods.updateUserInfo));
	}
	
	@Override
	public void submitReservation(Reservation reservation) {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_SUBMIT_RESERVATION + reservation.getSubmitReservationParams(), new MethodParams(Methods.submitReservation));
	}
	
	@Override
	public void closeReservation(Reservation reservation) {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_CLOSE_RESERVATION + reservation.getCloseReservationParams(), new MethodParams(Methods.closeReservation));
	}
	
	@Override
	public void updateReservation(Reservation reservation) {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_UPDATE_RESERVATION + reservation.getUpdateReservationParams(), new MethodParams(Methods.updateReservation));
	}
	
	@Override
	public void getListReservation(String userID) {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_GET_LIST_RESERVATION + userID, new MethodParams(Methods.getListReservation));
	}

	@Override
	public void getUpdateDishs(String time, String dishIdList) {
		// TODO Auto-generated method stub
		String url = String.format(Urls.LINK_TO_CHECK_UPDATE_DISHS, time, dishIdList);
		excute(url, new MethodParams(Methods.getUpdateDishs));
	}

	@Override
	public void getPlaces() {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_GET_PLACES, new MethodParams(Methods.getPlaces));
	}

	@Override
	public void addPreOrder(String generateID, String currentDateOrder, String currentTimeOrder, String dateOrder, String timeOrder, String placeID){
		// TODO Auto-generated method stub
		String url = String.format(Urls.LINK_TO_ADD_PRE_ORDER, generateID, currentDateOrder, currentTimeOrder, dateOrder, timeOrder, placeID);
		excute(url, new MethodParams(Methods.addPreOrder));
	}

	@Override
	public void addPreOrderDetail(String url) {
		// TODO Auto-generated method stub
		excute(url, new MethodParams(Methods.addPreOrderDetail));
	}

	@Override
	public void getListOrderAndDetail(String generateID) {
		// TODO Auto-generated method stub
		String url = String.format(Urls.LINK_TO_GET_LIST_ORDER_AND_DETAIL, generateID);
		excute(url, new MethodParams(Methods.getListOrderAndDetail));
	}
	
	@Override
	public void getAboutUs() {
		// TODO Auto-generated method stub
		excute(Urls.LINK_TO_GET_ABOUT_US, new MethodParams(Methods.getAboutUs));
	}

	@Override
	public void cancelPreOrder(String orderID) {
		// TODO Auto-generated method stub
		String url = String.format(Urls.LINK_TO_CANCEL_PRE_ORDER, orderID);
		excute(url, new MethodParams(Methods.cancelPreOrder));
	}

	@Override
	public void editYourPreOrder(String order_id, String order_current_date,
			String order_current_time, String order_date, String order_time,
			String order_place) {
		// TODO Auto-generated method stub
		String url = String.format(Urls.LINK_TO_EDIT_PRE_ORDER, order_id, order_current_date, order_current_time, order_date, order_time, order_place);
		excute(url, new MethodParams(Methods.editYourPreOrder));
	}
}
